#ifndef _KINECT_h_
#define _KINECT_h_

#include "utility.h"

#include <OgreSingleton.h>
#include "Ogre.h"
#include "OIS.h"

#include <XnVDeviceGenerator.h>
#include <XnVNite.h>

#include <XnTypes.h>
#include <XnV3DVector.h>

#include <OgreStringConverter.h>
#include <OgreErrorDialog.h>

#include "SkeletonPoseDetector.h"
#include "MouseMessage.h"

class Kinect: public Ogre::Singleton<Kinect>
{
public:
	Kinect();
	~Kinect();

	XnStatus rc;
	xn::Context m_Context;
	xn::UserGenerator m_UserGenerator;
	xn::HandsGenerator m_HandsGenerator;
	xn::GestureGenerator m_GestureGenerator;
	xn::SceneAnalyzer m_SceneAnalyzer;

	XnVSessionManager* m_pSessionManager;
	XnVFlowRouter* m_pQuitFlow;
	XnVSelectableSlider1D* m_pQuitSSlider;

	XnUserID m_candidateID;

	XnCallbackHandle m_hPoseCallbacks;
	XnCallbackHandle m_hUserCallbacks;
	XnCallbackHandle m_hCalibrationCallbacks;
	XnCallbackHandle m_hHandCallbacks;
	XnCallbackHandle m_hGestureCallbacks;

	StartPoseDetector * m_pStartPoseDetector;
	EndPoseDetector * m_pEndPoseDetector;

	XnSkeletonJointPosition jointPos;

	int isHandTracking[MAX_HAND_NUM];

	void initPrimeSensor();
	XnStatus BootKinect();

	void Pause();
	void Restart();

	static void XN_CALLBACK_TYPE NewUser(xn::UserGenerator& generator, const XnUserID nUserId, void* pCookie)
	{
		PRINT("New User Identified!");
		// start looking for calibration pose for new users
		generator.GetPoseDetectionCap().StartPoseDetection("Psi", nUserId);
	}

	static void XN_CALLBACK_TYPE LostUser(xn::UserGenerator& generator, const XnUserID nUserId, void* pCookie)
	{
		PRINT("User Lost!");
		if(Kinect::getSingleton().m_candidateID == nUserId )
		{
			Kinect::getSingleton().m_candidateID = 0;
			Kinect::getSingleton().isLost = true;
			Kinect::getSingleton().m_pEndPoseDetector->SetUserId(0);
			Kinect::getSingleton().m_pStartPoseDetector->Reset();
		}
	}

	static void XN_CALLBACK_TYPE CalibrationStart(xn::SkeletonCapability& skeleton, const XnUserID nUserId, void* pCookie)
	{
	}

	static void XN_CALLBACK_TYPE CalibrationEnd(xn::SkeletonCapability& skeleton, const XnUserID nUserId, XnBool bSuccess, void* pCookie)
	{
		PRINT("Calibration complete for user!");
		if (bSuccess)
		{
			// start tracking
			skeleton.StartTracking(nUserId);
			
			Kinect::getSingleton().m_pStartPoseDetector->SetStartPoseState(true);
			Kinect::getSingleton().m_pEndPoseDetector->SetUserId(nUserId);

			// save torso position
			XnSkeletonJointPosition torsoPos;
			skeleton.GetSkeletonJointPosition(nUserId, XN_SKEL_TORSO, torsoPos);
			Kinect::getSingleton().TorsoPos.x = -torsoPos.position.X;
			Kinect::getSingleton().TorsoPos.y = torsoPos.position.Y;
			Kinect::getSingleton().TorsoPos.z = -torsoPos.position.Z;

			Kinect::getSingleton().m_pQuitFlow->SetActive(NULL);

			Kinect::getSingleton().suppress = true;
		}
		else
		{
			Kinect::getSingleton().m_candidateID = 0;
		}
	}

	static void XN_CALLBACK_TYPE PoseDetected(xn::PoseDetectionCapability& poseDetection, const XnChar* strPose, XnUserID nId, void* pCookie)
	{
		if(Kinect::getSingleton().m_candidateID == 0)
		{
			Kinect::getSingleton().m_candidateID = nId;
			Kinect::getSingleton().m_UserGenerator.GetSkeletonCap().RequestCalibration(nId, TRUE);
			Kinect::getSingleton().m_pStartPoseDetector->SetStartPoseState(true);
		}
	}

	static void XN_CALLBACK_TYPE PoseLost(xn::PoseDetectionCapability& poseDetection, const XnChar* strPose, XnUserID nId, void* pCookie)
	{
		Kinect::getSingleton().m_pStartPoseDetector->Reset();
	}

	static void XN_CALLBACK_TYPE quitSSliderPPC(const XnVHandPointContext* pContext, const XnPoint3D& ptFocus, void* cxt)
	{
		if (Kinect::getSingleton().suppress)
		{
			return;
		}
	}

	static void XN_CALLBACK_TYPE quitSSliderPPD(XnUInt32 nID, void* cxt)
	{
	}

	static void XN_CALLBACK_TYPE quitSSliderVC(XnFloat fValue, void* cxt)
	{
		// reverse value if we are mirrored
		if(!Kinect::getSingleton().m_front)
		{
			fValue = 1-fValue;
		}

		// quit or return to demo on slider edges
		if(fValue > 0.99)
		{
			exit(0);
		} 

		else if (fValue < 0.01)
		{
			Kinect::getSingleton().m_pSessionManager->EndSession();
		}
	}

	static void XN_CALLBACK_TYPE quitSSliderOAM(XnVDirection dir, void* cxt)
	{
	}

	static void XN_CALLBACK_TYPE gestureRecog( xn::GestureGenerator &generator,  
								        const XnChar *strGesture,  
                                        const XnPoint3D *pIDPosition,  
                                        const XnPoint3D *pEndPosition,  
                                        void *pCookie )  
	{
		//std::cout<<"Gesture Recognizing: "<<strGesture<<" from "<<pIDPosition->X<<","<<pIDPosition->Z<<","<<pIDPosition->Z<<" to "<<pEndPosition->X<<","<<pEndPosition->Z<<","<<pEndPosition->Z<<std::endl;
		int imgStartX=(int)pIDPosition->X;
		int imgStartY=(int)pIDPosition->Y;

		if(strcmp(strGesture,"Click")==0)
		{
			PRINT("StartTracking!");
			Kinect::getSingleton().m_HandsGenerator.StartTracking(*pEndPosition);
		}
		else if(strcmp(strGesture,"Wave")==0)
		{
			PRINT("Mouse Clicked!");
			MouseMessage::getSingleton().updateMouse(myPoint(imgStartX,imgStartY),0,MOUSE_CLICK);
		}
	}

	static void XN_CALLBACK_TYPE gestureProgress( xn::GestureGenerator &generator,  
										   const XnChar *strGesture,  
										   const XnPoint3D *pPosition,  
										   XnFloat fProgress,  
										   void *pCookie )  
	{
		//std::cout<<"Gesture Processing " << strGesture << ":" << fProgress << " at " <<pPosition->X<<","<<pPosition->Z<<","<<pPosition->Z << std::endl;
	}  

	static void XN_CALLBACK_TYPE Hand_Create(xn::HandsGenerator& generator, XnUserID nId, const XnPoint3D* pPosition, XnFloat fTime, void* pCookie)
	{ 
		std::cout<<"New Hand "<<nId<<" At: "<<pPosition->X<<","<<pPosition->Z<<","<<pPosition->Z<<std::endl;
		Kinect::getSingleton().isHandTracking[nId] = 1;
	}

	static void XN_CALLBACK_TYPE Hand_Update(xn::HandsGenerator& generator, XnUserID nId, const XnPoint3D* pPosition, XnFloat fTime, void* pCookie)
	{
		int imgPosX=(int)pPosition->X;
		int imgPosY=(int)pPosition->Y;
		MouseMessage::getSingleton().updateMouse(myPoint(imgPosX,imgPosY),nId,MOUSE_MOVE);
	} 

	static void XN_CALLBACK_TYPE Hand_Destroy(xn::HandsGenerator& generator, XnUserID nId, XnFloat fTime, void* pCookie)
	{
		//std::cout<<"Lost Hand: "<<nId<<std::endl;
		Kinect::getSingleton().isHandTracking[nId] = 0;
	}


	Ogre::Quaternion getJointOrientation(XnSkeletonJoint skelJoint);
	Ogre::Vector3 getJointPos(XnSkeletonJoint skelJoint);

	bool isLost;
	double m_SmoothingFactor;
	int m_SmoothingDelta;
	Ogre::Vector3 TorsoPos;
	bool suppress;
	bool m_front;
	bool isPause;
	bool isDown;
};

#endif